import {createComponentInstance, setupComponent} from "./component";
// @ts-ignore
import {EMPTY_OBJ, isObject} from "../shared";
import {ShapeFlags} from "../shared/ShapeFlags";
import {Fragment, Text} from "./vnode";
import {createAppAPI} from "./createApp";
import {effect} from "../reactivity/effect";
import {shouldUpdateComponent} from "./ComponentUpdateUtils";
import {insert} from "../runtime-dom";


export function createRenderer(options) {
    const {
        createElement,
        patchProps: hostPatchProps,
        insert: hostInsert,
        remove: hostRemove,
        setElementText: hostSetElementText
    } = options;


    function render(vnode, container) {
        /**
         * patch 后续是需要递归调用的
         */
        // 如果是第一次调用是虚拟节点挂载到跟容器上
        patch(null, vnode, container, null, null);
    }

    /**
     *
     * @param n1 老的虚拟节点
     * @param n2 新的虚拟节点
     * @param container
     * @param parentComponent
     */
    function patch(n1, n2, container, parentComponent, anchor) {
        // 解构出shapeFlags,用户判断传入的虚拟节点是一个html元素,还是一个有状态的组件
        const {shapeFlags, type} = n2;

        // Fragment 类型只处理子节点,
        switch (type) {
            case Fragment:
                processFragment(n1, n2, container, parentComponent, anchor);
                break;
            case Text:
                processText(n1, n2, container, parentComponent, anchor);
                break;
            default:
                // 如果是一个element,那么就处理element
                // 如果 vnode的type是string的话那么就是一个element
                //  通过 & 操作来判断是不是ELEMENT类型,这里还隐含着一个强转, 如果或运算的结果是大于1的,说明是ELEMENT元素
                //  会强转成true,如果位运算得到的值是0,会被强转成false.
                if (shapeFlags & ShapeFlags.ELEMENT) {
                    // 处理html 元素
                    processElement(n1, n2, container, parentComponent, anchor);
                } else if (shapeFlags & ShapeFlags.STATEFUL_COMPONENT) {
                    //  如果是一个Component类型
                    processComponent(n1, n2, container, parentComponent, anchor)
                }
                break;
        }


    }

    function processElement(n1, n2, container, parentComponent, anchor) {
        // 如果n1元素不存在
        if (!n1) {
            mountElement(n2, container, parentComponent, anchor);
        } else {
            patchElement(n1, n2, container, parentComponent, anchor);
        }

    }

    // 更新属性.
    function patchElement(n1, n2, container, parentComponent, anchor) {
        console.log("n1", n1);
        console.log("n2", n2);
        console.log("container", container);
        // 之前对象的props
        const oldProps = n1.props || EMPTY_OBJ;
        // 现在对象的props
        const newProps = n2.props || EMPTY_OBJ;
        // 因为更新元素不会在执行 mountElement 函数
        // 也就是不会给n2的el属性赋值，所以此处要把n1的el也赋给n2的el一份
        const el = (n2.el = n1.el);

        // 更新children
        patchChildren(n1, n2, container, parentComponent, anchor);
        //  更新属性
        patchProps(el, oldProps, newProps);

    }

    function patchChildren(n1, n2, container, parentComponent, anchor) {
        const {shapeFlags: nextShapeFlags, children: c2} = n2;
        const {shapeFlags: prevShapeFlags, children: c1} = n1;
        //   首先是判断n1虚拟节点的类型和n2虚拟节点的类型,
        //    如果
        if (ShapeFlags.TEXT_CHILDREN & nextShapeFlags) {
            if (c1 !== c2) {
                hostSetElementText(container, c2)
            }
        } else {
            // if (ShapeFlags.ARRAY_CHILDREN & prevShapeFlags) {
            //     umountChildren(n1.children);
            // }
            if (prevShapeFlags & ShapeFlags.TEXT_CHILDREN) {
                hostSetElementText(container, "");
                mountChildren(n2.children, container, parentComponent, anchor)
            } else {
                // 数组对比数组的情况

                patchKeyedChildren(c1, c2, container, parentComponent, anchor);

            }
        }

    }

    function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
        // 左侧开始移动的指针
        let i = 0;
        // 第一个数组最后一个元素的索引
        let e1 = c1.length - 1;
        // 第二个数组最后一个元素的索引
        let e2 = c2.length - 1;
        // region        找到左侧的边界
        //  从左到右对比每一个元素
        while (i <= e1 && i <= e2) {
            const n1 = c1[i];
            const n2 = c2[i];
            //  如果两个虚拟节点相等,那么就递归比较
            if (isSameVnodeType(n1, n2)) {
                patch(n1, n2, container, parentComponent, parentAnchor)
            } else {
                break;
            }
            i++;
        }

        // endregion

        //region   找到右侧的边界
        while (i <= e1 && i <= e2) {
            const n1 = c1[e1];
            const n2 = c2[e2];
            //  如果两个虚拟节点相等,那么就递归比较
            if (isSameVnodeType(n1, n2)) {
                patch(n1, n2, container, parentComponent, parentAnchor)
            } else {
                break;
            }
            e1--;
            e2--;
        }
        // endregion

        //  region 新的比老的长, 需要新增
        if (i > e1 && i <= e2) {
            const nextPos = e2 + 1;
            const anchor = nextPos < c2.length ? c2[nextPos].el : parentAnchor;
            const parent = anchor ? anchor.parentNode : container;
            while (i <= e2) {
                patch(null, c2[i], parent, parentComponent, anchor);
                i++;
            }
            // endregion
            //  region 老的比新的长, 需要删除
        } else if (i > e2) {
            while (i <= e1) {
                hostRemove(c1[i].el);
                i++;
            }
            // endregion
            //  region 中间对比
        } else {
            // 两个数组开始不同的起点
            let s1 = i;
            let s2 = i;

            const toBePatched = e2 - s2 + 1;
            let patched = 0;
            // 新数组索引和老数组索引的映射表
            // 定长数组性能最佳,所以此处使用定长数组
            const newIndexToOldIndexMap = new Array(toBePatched);
            //  如果后一个小于前一个,那么它就是需要移动位置的.
            let moved = false;
            // 记录最大值
            let maxNewIndexSoFar = 0;
            // 初始化 映射关系
            for (let j = 0; j < toBePatched; j++) {
                newIndexToOldIndexMap[j] = 0;
            }

            //     先对新数组不同的部分生成映射表
            const keyToNewIndexMap: Map<string, number> = new Map();
            for (let newDiffIndex = s2; newDiffIndex <= e2; newDiffIndex++) {
                const nextChild = c2[newDiffIndex];
                keyToNewIndexMap.set(nextChild.key, newDiffIndex);
            }
            //     遍历老数组不同的部分
            for (let oldDiffIndex = s1; oldDiffIndex <= e1; oldDiffIndex++) {
                const prevChild = c1[oldDiffIndex];
                //   暂时注掉这个地方,感觉有点不太对
                // if (patched >= toBePatched){
                //     hostRemove(prevChild.el);
                //     continue;
                // }

                let newIndex;
                if (prevChild.key) {
                    newIndex = keyToNewIndexMap.get(prevChild.key);
                }
                //    如果没有写key属性
                else {
                    for (let j = s2; j <= e2; j++) {
                        if (isSameVnodeType(prevChild, c2[j])) {
                            newIndex = j;
                            break;
                        }
                    }
                }
                //   如果newIndex等于undefined那么就说明这个元素在新数组中不存在,要被删除
                if (newIndex === undefined) {
                    hostRemove(prevChild.el)
                } else {

                    if (newIndex > maxNewIndexSoFar) {
                        maxNewIndexSoFar = newIndex;
                        moved = false;
                    } else {
                        moved = true;
                    }
                    // 此处可以确认新的节点是存在的，也就是从keyToNewIndexMap,或者新的数组中取到了值。
                    // i表示的是旧数组中元素的位置，newIndex - s2 表示的新不同数组的中元素的位置
                    newIndexToOldIndexMap[newIndex - s2] = oldDiffIndex + 1;
                    patch(prevChild, c2[newIndex], container, parentComponent, null);
                    patched++;
                }
            }
            const increasingNewIndexSequence = moved
                ? getSequence(newIndexToOldIndexMap)
                : []
            // 循环最长子序列的指针
            let j = increasingNewIndexSequence.length - 1;

            for (let k = toBePatched - 1; k >= 0; k--) {
                const nextIndex = k + s2;
                const nextChild = c2[nextIndex];
                const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null;
                const parent = anchor ? anchor.parentNode : container;
                // 创建新元素
                if (newIndexToOldIndexMap[k] === 0) {
                    patch(null, nextChild, parent, parentComponent, anchor);
                }

                if (moved) {
                    if (j < 0 || k !== increasingNewIndexSequence[j]) {
                        console.log("移动位置")
                        hostInsert(nextChild.el, parent, anchor)
                    } else {
                        j--
                    }
                }
            }


        }
        // endregion

        //  region 老的比新的长, 需要删除


    }

    function isSameVnodeType(n1, n2) {
        return n1.type === n2.type && n1.key === n2.key
    }

    function umountChildren(children) {
        //     删除元素
        for (let i = 0; i < children.length; i++) {
            const el = children[i].el;
            hostRemove(el)
        }
    }


    function patchProps(el, oldProps, newProps) {
        //     遍历新对象的所有属性,如果从新对象中取出的值和从旧对象中取出的值不一样,
        //     那么就调用patchProp 函数更新属性
        for (let key in newProps) {
            const prevProp = oldProps[key];
            const nextProp = newProps[key];
            if (nextProp !== prevProp) {
                hostPatchProps(el, key, prevProp, nextProp)
            }
        }

        if (oldProps !== EMPTY_OBJ) {
            //   如果老对象中属性在新对象中没有了，那么就在el中删掉这个属性
            for (const key in oldProps) {
                if (!(key in newProps)) {
                    hostPatchProps(el, key, oldProps[key], null)
                }
            }
        }
    }


    function mountElement(vnode, container, parentComponent, anchor) {
        //  可以通过vnode.el 存储挂载的节点元素
        // debugger
        //  修改成依赖接口而不是具体的诗仙,为了便于扩展
        // const el = (vnode.el = document.createElement(vnode.type));
        const el = (vnode.el = createElement(vnode.type));

        const {children, props, shapeFlags} = vnode;

        if (shapeFlags & ShapeFlags.TEXT_CHILDREN) {
            el.textContent = children;
        } else if (shapeFlags & ShapeFlags.ARRAY_CHILDREN
        ) {
            mountChildren(vnode.children, el, parentComponent, anchor);
        }

        for (let key in props) {
            const val = props[key];
            console.log(key)

            hostPatchProps(el, key, null, val);
        }
        // container.append(el);
        hostInsert(el, container, anchor);
    }

    /**
     * 处理组件类型
     * @param n2  虚拟节点类型,
     * @param container 父容器
     */
    function processComponent(n1, n2, container, parentComponent, anchor) {
        if (!n1) {
            mountComponent(n2, container, parentComponent, anchor)
        } else {
            updateComponent(n1, n2);
        }

    }


    function updateComponent(n1, n2) {
        // 更新组件,重新渲染虚拟节点
        // instance 从哪里来呢，可以把instance挂载带虚拟节点上。
        const instance = (n2.component = n1.component);
        // 判断组件是否需要更新
        if (shouldUpdateComponent(n1, n2)) {
            // 存储n2,方便setupRenderEffect函数使用组件更新后的props
            instance.next = n2;
            instance.update()
        } else {
            //     不要更新,虚拟节点也是需要重置的
            n2.el = n1.el;
            instance.vnode = n2;
        }
    }

    /**
     *  挂载组件
     * @param initinalVnode  初始化节点
     * @param container
     */
    function mountComponent(initinalVnode, container, parentComponent, anchor) {
        // 创建组件实例
        //  将组件编程对象,后序对slots和emit等操作就变得容易了
        const instance = (initinalVnode.component = createComponentInstance(
            initinalVnode, parentComponent
        ));
        // 初始化组件
        setupComponent(instance);
        // 初始化渲染副作用函数
        setupRenderEffect(instance, initinalVnode, container, anchor)
    }

    function setupRenderEffect(instance, initinalVnode, container, anchor) {
        // 响应式对象变化的时候，重新生成虚拟节点。所以把生成虚拟节点的过程放到effect函数里面。
        // 执行render函数时会对变量做依赖收集。
        // 把runner赋给实例的update属性,方便组件更新时再次调用.
        instance.update = effect(() => {
            // 如果没有挂载，是第一操作，那么直接插入
            if (!instance.isMounted) {
                console.log("init")
                //  把这个代理对象绑定到render的this上
                const {proxy} = instance;
                //  执行组件实例的render函数,将结果赋给subtree函数
                //  subTree 指的是组件示例调用render函数生成的虚拟节点,
                //  这个render函数是用户声明有状态的组件时写的,不是renderer.ts文件中的render函数
                //    虚拟节点树,把proxy绑定到render的this上去.

                // 把生成的虚拟节点放到组件实例的subtree 属性上一份，方便待会更新的时候对比
                const subTree = (instance.subTree = instance.render.call(proxy))
                console.log(subTree)


                // vnode -> patch
                //     vnode -> element->mountElement
                //   递归调用,一直到组件是html标签
                patch(null, subTree, container, instance, anchor)
                //   处理虚拟节点的el 的时机应该是什么呢? 就是所有element 元素都挂载完,这个时候el 肯定是有值的
                //   self.$el
                //   <div id="root" class="red,hard">Hi mini-vue-haha</div>
                initinalVnode.el = subTree.el
                //  挂载  完毕
                instance.isMounted = true;
            } else {
                console.log("update")

                // 需要一个vnode,拿到el，更新el
                const {next, vnode} = instance;
                if (next) {
                    // 更细el
                    next.el = vnode.el
                    // 渲染真实dom之前更新一下组件???
                    updateComponentPreRender(instance, next);
                }
                //  把这个代理对象绑定到render的this上
                const {proxy} = instance;
                //  执行组件实例的render函数,将结果赋给subtree函数
                //  subTree 指的是组件示例调用render函数生成的虚拟节点,
                //  这个render函数是用户声明有状态的组件时写的,不是renderer.ts文件中的render函数
                //    虚拟节点树,把proxy绑定到render的this上去.

                // 把生成的虚拟节点放到组件实例的subtree 属性上一份，方便待会更新的时候对比
                const subTree = instance.render.call(proxy)
                // 取出第一次插入操作时的存入的虚拟节点，开始比较。
                const prevSubTree = instance.subTree;
                // 更新subtree
                instance.subTree = subTree;
                // console.log("current", subTree);
                // console.log("prev", prevSubTree);


                // vnode -> patch
                //     vnode -> element->mountElement
                //   递归调用,一直到组件是html标签
                patch(prevSubTree, subTree, container, instance, anchor)
                //   处理虚拟节点的el 的时机应该是什么呢? 就是所有element 元素都挂载完,这个时候el 肯定是有值的
                //   self.$el
                //   <div id="root" class="red,hard">Hi mini-vue-haha</div>
                initinalVnode.el = subTree.el

            }

        })

    }

    function updateComponentPreRender(instance, nextVnode) {
        //     更新props
        instance.props = nextVnode.props;
        //     更新vnode
        instance.vnode = nextVnode;
        //     重置next
        instance.next = null
    }

    function processFragment(n1, n2, container, parentComponent, anchor) {
        // 把vnode的所有children 都给到container
        mountChildren(n2.children, container, parentComponent, anchor)
    }

    function mountChildren(children,
                           container: HTMLAnchorElement | HTMLElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | HTMLQuoteElement | HTMLBodyElement | HTMLBRElement | HTMLButtonElement | HTMLCanvasElement | HTMLTableCaptionElement | HTMLTableColElement | HTMLDataElement | HTMLDataListElement | HTMLModElement | HTMLDetailsElement | HTMLDialogElement | HTMLDivElement | HTMLDListElement | HTMLEmbedElement | HTMLFieldSetElement | HTMLFormElement | HTMLHeadingElement | HTMLHeadElement | HTMLHRElement | HTMLHtmlElement | HTMLIFrameElement | HTMLImageElement | HTMLInputElement | HTMLLabelElement | HTMLLegendElement | HTMLLIElement | HTMLLinkElement | HTMLMapElement | HTMLMenuElement | HTMLMetaElement | HTMLMeterElement | HTMLObjectElement | HTMLOListElement | HTMLOptGroupElement | HTMLOptionElement | HTMLOutputElement | HTMLParagraphElement | HTMLPictureElement | HTMLPreElement | HTMLProgressElement | HTMLScriptElement | HTMLSelectElement | HTMLSlotElement | HTMLSourceElement | HTMLSpanElement | HTMLStyleElement | HTMLTableElement | HTMLTableSectionElement | HTMLTableCellElement | HTMLTemplateElement | HTMLTextAreaElement | HTMLTimeElement | HTMLTitleElement | HTMLTableRowElement | HTMLTrackElement | HTMLUListElement | HTMLVideoElement,
                           parentComponent,
                           anchor) {
        children.forEach(v => {
            patch(null, v, container, parentComponent, anchor);
        })
    }


    function processText(n1, n2, container, parentComponent, anchor) {
        const {children} = n2;
        const textNode = (n2.el = document.createTextNode(children));
        container.append(children);
    }

    return {
        createApp: createAppAPI(render)
    }
}

function getSequence(arr: number[]): number[] {
    const p = arr.slice();
    const result = [0];
    let i, j, u, v, c;
    const len = arr.length;
    for (i = 0; i < len; i++) {
        const arrI = arr[i];
        if (arrI !== 0) {
            j = result[result.length - 1];
            if (arr[j] < arrI) {
                p[i] = j;
                result.push(i);
                continue;
            }
            u = 0;
            v = result.length - 1;
            while (u < v) {
                c = (u + v) >> 1;
                if (arr[result[c]] < arrI) {
                    u = c + 1;
                } else {
                    v = c;
                }
            }
            if (arrI < arr[result[u]]) {
                if (u > 0) {
                    p[i] = result[u - 1];
                }
                result[u] = i;
            }
        }
    }
    u = result.length;
    v = result[u - 1];
    while (u-- > 0) {
        result[u] = v;
        v = p[v];
    }
    return result;
}